#include "U2GLESHardwareBufferManager.h"
#include "U2GLESHardwareVertexBuffer.h"
#include "U2GLESHardwareIndexBuffer.h"
#include "U2HardwareBuffer.h"


U2EG_NAMESPACE_USING


//-----------------------------------------------------------------------
// Scratch pool management (32 bit structure)
struct GLESScratchBufferAlloc
{
    /// Size in bytes
    u2uint32 size: 31;
    /// Free? (pack with size)
    u2uint32 free: 1;
};
#define SCRATCH_POOL_SIZE 1 * 1024 * 1024
#define SCRATCH_ALIGNMENT 32

GLESHardwareBufferManagerBase::GLESHardwareBufferManagerBase()
{
    // Init scratch pool
    // TODO make it a configurable size?
    // 32-bit aligned buffer
    mScratchBufferPool = static_cast<char*>(U2_MALLOC_ALIGN(SCRATCH_POOL_SIZE,
                                                              MEMCATEGORY_GEOMETRY,
                                                              SCRATCH_ALIGNMENT));
    GLESScratchBufferAlloc* ptrAlloc = (GLESScratchBufferAlloc*)mScratchBufferPool;
    ptrAlloc->size = SCRATCH_POOL_SIZE - sizeof(GLESScratchBufferAlloc);
    ptrAlloc->free = 1;

    // non-Win32 machines are having issues glBufferSubData, looks like buffer corruption
	// disable for now until we figure out where the problem lies			
#	if U2_PLATFORM != U2_PLATFORM_WIN32
	mMapBufferThreshold = 0;
#	endif
}

GLESHardwareBufferManagerBase::~GLESHardwareBufferManagerBase()
{
    destroyAllDeclarations();
    destroyAllBindings();

    U2_FREE_ALIGN(mScratchBufferPool, MEMCATEGORY_GEOMETRY, SCRATCH_ALIGNMENT);
}

U2HardwareVertexBufferSharedPtr
    GLESHardwareBufferManagerBase::createVertexBuffer(size_t vertexSize,
                                                  size_t numVerts,
                                                  U2HardwareBuffer::Usage usage,
                                                  bool useShadowBuffer)
{
    // always use shadowBuffer
    GLESHardwareVertexBuffer* buf =
        U2_NEW GLESHardwareVertexBuffer(this, vertexSize, numVerts, usage, true);
    {
        U2_LOCK_MUTEX(mVertexBuffersMutex)
        mVertexBuffers.insert(buf);
    }
    return U2HardwareVertexBufferSharedPtr(buf);
}

U2HardwareIndexBufferSharedPtr GLESHardwareBufferManagerBase::createIndexBuffer(U2HardwareIndexBuffer::IndexType itype,
                                                                          size_t numIndexes,
                                                                          U2HardwareBuffer::Usage usage,
                                                                          bool useShadowBuffer)
{
    // always use shadowBuffer
    GLESHardwareIndexBuffer* buf =
        U2_NEW GLESHardwareIndexBuffer(this, itype, numIndexes, usage, true);
    {
        U2_LOCK_MUTEX(mIndexBuffersMutex)
        mIndexBuffers.insert(buf);
    }
    return U2HardwareIndexBufferSharedPtr(buf);
}


GLenum GLESHardwareBufferManagerBase::getGLUsage(unsigned int usage)
{
    switch(usage)
    {
        case U2HardwareBuffer::HBU_STATIC:
        case U2HardwareBuffer::HBU_STATIC_WRITE_ONLY:
            return GL_STATIC_DRAW;
        case U2HardwareBuffer::HBU_DYNAMIC:
        case U2HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY:
            return GL_DYNAMIC_DRAW;
        case U2HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE:
        default:
            return GL_DYNAMIC_DRAW;
    };
}

GLenum GLESHardwareBufferManagerBase::getGLType(unsigned int type)
{
    switch(type)
    {
        case VET_FLOAT1:
        case VET_FLOAT2:
        case VET_FLOAT3:
        case VET_FLOAT4:
            return GL_FLOAT;
        case VET_SHORT1:
        case VET_SHORT2:
        case VET_SHORT3:
        case VET_SHORT4:
            return GL_SHORT;
        case VET_COLOUR:
        case VET_COLOUR_ABGR:
        case VET_COLOUR_ARGB:
        case VET_UBYTE4:
            return GL_UNSIGNED_BYTE;
        default:
            return 0;
    };
}

void* GLESHardwareBufferManagerBase::allocateScratch(u2uint32 size)
{
    // simple forward link search based on alloc sizes
    // not that fast but the list should never get that long since not many
    // locks at once (hopefully)
    U2_LOCK_MUTEX(mScratchMutex)

    // Alignment - round up the size to 32 bits
    // control blocks are 32 bits too so this packs nicely
    if (size % 4 != 0)
    {
        size += 4 - (size % 4);
    }

    u2uint32 bufferPos = 0;
    while (bufferPos < SCRATCH_POOL_SIZE)
    {
        GLESScratchBufferAlloc* pNext = (GLESScratchBufferAlloc*)(mScratchBufferPool + bufferPos);
        // Big enough?
        if (pNext->free && pNext->size >= size)
        {
            // split? And enough space for control block
            if(pNext->size > size + sizeof(GLESScratchBufferAlloc))
            {
                u2uint32 offset = sizeof(GLESScratchBufferAlloc) + size;

                GLESScratchBufferAlloc* pSplitAlloc = (GLESScratchBufferAlloc*)
                    (mScratchBufferPool + bufferPos + offset);
                pSplitAlloc->free = 1;
                // split size is remainder minus new control block
                pSplitAlloc->size = pNext->size - size - sizeof(GLESScratchBufferAlloc);

                // New size of current
                pNext->size = size;
            }
            // allocate and return
            pNext->free = 0;

            // return pointer just after this control block (++ will do that for us)
            return ++pNext;
        }

        bufferPos += sizeof(GLESScratchBufferAlloc) + pNext->size;
    }

    // no available alloc
    return 0;
}

void GLESHardwareBufferManagerBase::deallocateScratch(void* ptr)
{
    U2_LOCK_MUTEX(mScratchMutex)

    // Simple linear search dealloc
    u2uint32 bufferPos = 0;
    GLESScratchBufferAlloc* pLast = 0;
    while (bufferPos < SCRATCH_POOL_SIZE)
    {
        GLESScratchBufferAlloc* pCurrent = (GLESScratchBufferAlloc*)(mScratchBufferPool + bufferPos);

        // Pointers match?
        if ((mScratchBufferPool + bufferPos + sizeof(GLESScratchBufferAlloc)) == ptr)
        {
            // dealloc
            pCurrent->free = 1;
            // merge with previous
            if (pLast && pLast->free)
            {
                // adjust buffer pos
                bufferPos -= (pLast->size + sizeof(GLESScratchBufferAlloc));
                // merge free space
                pLast->size += pCurrent->size + sizeof(GLESScratchBufferAlloc);
                pCurrent = pLast;
            }

            // merge with next
            u2uint32 offset = bufferPos + pCurrent->size + sizeof(GLESScratchBufferAlloc);
            if (offset < SCRATCH_POOL_SIZE)
            {
                GLESScratchBufferAlloc* pNext = (GLESScratchBufferAlloc*)(
                    mScratchBufferPool + offset);
                if (pNext->free)
                {
                    pCurrent->size += pNext->size + sizeof(GLESScratchBufferAlloc);
                }
            }

            // done
            return;
        }

        bufferPos += sizeof(GLESScratchBufferAlloc) + pCurrent->size;
        pLast = pCurrent;

    }

    // Should never get here unless there's a corruption
    assert(false && "Memory deallocation error");
}
//---------------------------------------------------------------------
const size_t GLESHardwareBufferManagerBase::getGLMapBufferThreshold() const
{
	return mMapBufferThreshold;
}
//---------------------------------------------------------------------
void GLESHardwareBufferManagerBase::setGLMapBufferThreshold( const size_t value )
{
	mMapBufferThreshold = value;
}

